package com.cmcc.pcap.jpcap.capture;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.Properties;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.cmcc.pcap.jpcap.util.CommonUtils;
import com.cmcc.pcap.packet.capture.CallBack;
import com.cmcc.pcap.packet.util.FileUtils;
import com.cmcc.pcap.packet.util.PacketAnalysisConfig;
import com.sun.jna.NativeLibrary;

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;

public class JCaptures {
	private static Logger logger = LogManager.getLogger(JCaptures.class);

//	public boolean complete = false;//标识单次一个pcap文件处理过程是否全部完成

	// 标记测试数据包中是否有tcp包或者udp包，true表示有，false表示没有
	public boolean tcp_packet_exist;
	public boolean udp_packet_exist;
	// 标记tcp或者udp包是否处理完毕
	public boolean tcp_complete;
	public boolean udp_complete;
	// 此处是否要新增两个标记位，一个用来标识tcp，一个用来标识udp
	public boolean fileRealSerial = false;
	public String filter;
	private PacketHandler packetHandler;
	JpcapCaptor captor;
	public CallBack callBack;
	public CallBack tcpcallBack;
	public CallBack udpcallBack;
	

//20180420
//	private static JCaptures jCaptures;
//	public static JCaptures getInstance(){//懒汉...需要线程安全吗？
//		jCaptures = jCaptures == null ? new JCaptures() : jCaptures;
//		return jCaptures;
//	}	

	public JCaptures(String taskId, CallBack callBack, CallBack tcpcallBack, CallBack udpcallBack) {
		this.callBack = callBack;
		this.tcpcallBack = tcpcallBack;
		this.udpcallBack = udpcallBack;
		logger.info("< I just tell you , I have started >");
		logger.debug("\n < System Env >  : \n" + System.getenv().toString().replaceAll(",", "\n"));
		logger.debug("\n < System Properties >  : \n" + System.getProperties().toString().replaceAll(",", "\n"));
		prepareDependantDll();
		this.filter = PacketAnalysisConfig.getConfigAsString("jpcap.filter").trim();
		this.packetHandler = new PacketHandler(this, taskId);
		this.fileRealSerial = "FileRealSerial"
				.equalsIgnoreCase(PacketAnalysisConfig.getConfigAsString("pcap.deal.mode"));
		logger.info("FileRealSerial : [" + fileRealSerial + "]");
		logger.info("Jcap Filter : [" + filter + "]");
	}

	/**
	 * 停止抓取包,结束所有处理线程
	 */
	public void stop() {
		logger.info("[ stoping JCaptures ]");
		if (captor != null)
			captor.close();
		if (this.packetHandler != null) {
			this.packetHandler.stop();
			this.packetHandler = null;
		}
		logger.info("[ stoped JCaptures ]");
	}

	/**
	 * 从网卡抓包
	 * 
	 * @param deviceInfo
	 */
	public void captureFromDevice(String deviceInfo, PacketHandler packetHandler) {
//		if(packetHandler != null) 
//		    this.packetHandler = packetHandler;//允许接口调用者使用自己的packetHandler
//		else
//			this.packetHandler = new PacketHandler();//如果接口调用者没有传入packetHandler,new一个默认的packetHandler

		NetworkInterface device = findDevice(deviceInfo);
		if (device != null) {
			try {
				captor = JpcapCaptor.openDevice(device, 65535, false, 20);
				/**
				 * captor.setFilter(arg0, arg1);//TODO 设置过滤器,构建一些常用的过滤规则？
				 * captor.setFilter("tcp", true); jpcap.setFilter("tcp and src 172.16.101.99",
				 * true); 如何过滤HTTP？
				 */
				captor.setFilter("tcp", true);
				captor.loopPacket(-1, this.packetHandler);// 此处循环抓包，如果是从网卡抓，会一直在这执行

			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			logger.error("capture start failure  with deviceinfo :" + deviceInfo);
		}

	}

	/**
	 * 从单个pcap文件读取抓包. TODO 判断文件后缀类型？ 外部多次调用该方法时，每个文件串行处理
	 * 
	 * @param filePath
	 */
	public void captureFromFile(String filePath, PacketHandler packetHandler) {
//		if(packetHandler != null) 
//		    this.packetHandler = packetHandler;//允许接口调用者使用自己的packetHandler
//		else
//			this.packetHandler = new PacketHandler();//如果接口调用者没有传入packetHandler,new一个默认的packetHandler

		logger.debug("this is udp test !");
		try {
			captor = JpcapCaptor.openFile(filePath);
			/**
			 * captor.setFilter(arg0, arg1);//TODO 设置过滤器,构建一些常用的过滤规则？
			 * captor.setFilter("tcp", true); jpcap.setFilter("tcp and src 172.16.101.99",
			 * true); captor.setFilter("vlan", true); 如何过滤HTTP？
			 */

			logger.debug("filter is :" + filter);

			if (filter != null && !"".equals(filter)) {
				captor.setFilter(filter, true);
			}
			captor.loopPacket(-1, this.packetHandler);// 此处循环读文件，读完一个文件方法结束

			while (fileRealSerial) {// 此处决定读入单个文件的所有packet后，是否阻塞主线程，当前文件处理完时，跳出循环。
//				if(complete) {
				if (tcp_complete && udp_complete) {
					break;
				}
				Thread.sleep(1000);
			}

		} catch (IOException e) {
			logger.error("Some IOException", e);
		}
//		catch (InterruptedException e) {
//			logger.error("Some InterruptedException",e);
//		} 
		catch (Exception e) {
			logger.error("Some Exception", e);
		}
	}

	/**
	 * 从多个pcap文件读取抓包
	 * 
	 * @param filePaths
	 */
	public void captureFromFiles(String[] filePaths, PacketHandler packetHandler) {
		for (String filePath : filePaths) {
//			this.complete = false;
			this.tcp_complete = false;
			this.udp_complete = false;
			this.captureFromFile(filePath, packetHandler);
		}
	}

	/**
	 * 遍历一个目录
	 */
	public void captureFromDirectory(String dirPath, PacketHandler packetHandler) {

		FilenameFilter filenameFilter = new FilenameFilter() {
			@Override
			public boolean accept(File file, String name) {// 这里的name属性，表示文件或文件夹的名字，
															// 可以直接根据名字做前缀、后缀以及正则表达式等进行过滤，
															// 如：return name.startsWith("ra");
				return name.matches("(.*\\.pcap)|(.*\\.cap)");
				// return name.contains(".pcap");//性能是否不如正则表达式?
			}
		};

		File dir = new File(dirPath);
		if (!dir.isDirectory()) {
			logger.error("the input is not a directory ");
			return;
		}

		File[] files = dir.listFiles(filenameFilter);

		logger.info("the fileNumber is : " + files.length);
		// logger.info("the dir total length is : "+dir.length());//8192

		int totalBytes = 0;
		for (File file : files) {
			totalBytes += file.length();
			logger.info("the < " + file.getName() + " > file.lentgh is : " + file.length());
		}
		logger.info("the dir total length is : " + totalBytes);

		int dealTotalBytes = 0;
		for (File file : files) {
			dealTotalBytes += file.length();
//			this.complete = false;

			this.tcp_complete = false;
			this.udp_complete = false;

			this.captureFromFile(file.getAbsolutePath(), packetHandler);

			DecimalFormat decimalFormat = (DecimalFormat) DecimalFormat.getInstance();
			decimalFormat.setMaximumFractionDigits(2);
			decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
			float progressNum = (float) dealTotalBytes / (float) totalBytes * 100;

			String progressStr = decimalFormat.format(progressNum);
			logger.info("the pcap deal rate of progress is : " + progressStr + " %");
		}

	}

	/**
	 * deviceInfo 应该做到可以适配 mac或name mac eg: 2CF0EE057A0A
	 * 
	 * @param deviceInfo
	 * @return
	 */
	private NetworkInterface findDevice(String deviceInfo) {
		jpcap.NetworkInterface[] devices = JpcapCaptor.getDeviceList();
		logger.debug("device sum is :" + devices.length);

		if (devices.length < 0)
			return null;

		for (jpcap.NetworkInterface device : devices) {
			String mac = CommonUtils.bytesToHexString(device.mac_address);
			if (mac.equalsIgnoreCase(deviceInfo)) {
				logger.info("find the device with deviceinfo :" + deviceInfo);// Log
				return device;
			}

			if (device.name.equalsIgnoreCase(deviceInfo)) {
				return device;
			}
		}
		logger.error("can not find the device with input deviceinfo :" + deviceInfo);// Log
		logger.error("the device list is :");// Log
		int i = 0;
		for (jpcap.NetworkInterface device : devices) {
			String mac = CommonUtils.bytesToHexString(device.mac_address);
			logger.error("    device " + i++ + " : <" + device.name + "> <" + mac + ">"); // Log
		}

		return null;

	}

	/**
	 * 目前采用的方法其实并不那么美丽，但是确实解决了Windows下的Dll依赖问题
	 * 
	 * 判断当前JDK/JRE的操作位数，sun.arch.data.model=32 或者64,然后操作位数之后，copy对应的Jpcap.dll文件到根目录下
	 * 
	 * Jpcap运行时会去sun.boot.library.path查找Jpcap.dll动态链接库
	 * 默认如果32位JDK/JRE，会去SysWow64下查找，64位JDK/JRE会去System32下查找（JRE的bin目录也是查找路径）
	 * 当前工程根目录也是查找路径，所以这里用该方法决定将哪个Jpcap.dll放到路径下
	 * 
	 * Win/Linux都在sun.boot.library.path查找动态链接库dll或so，win通常是对应jre下的bin目录下，Linux通常是jre下的lib下的amd64目录
	 */
	public void prepareDependantDll() {
		String os_name = (String) System.getProperties().get("os.name");
		String architecture = String.valueOf(System.getProperties().get("sun.arch.data.model"));// sun.arch.data.model表示JDK/JRE的操作位数，os.arch表示操作系统的操作位数
		logger.info("os.name : " + os_name);
		logger.info("sun.arch.data.model : " + architecture);
		String sun_boot_library_path = (String) System.getProperties().get("sun.boot.library.path");
		logger.info("sun.boot.library.path : " + sun_boot_library_path);
		if (!os_name.toLowerCase().contains("window")) {
			// linux时处理,仅支持64位操作系统
			File dest = new File(sun_boot_library_path + File.separator + "libjpcap.so");
			if (!dest.exists()) {
				try {
					Resource soPath = this.getFilePath("64/libjpcap.so");
					File soSource = soPath.getFile();
					if (!soSource.exists()) {
						logger.error("can not find libjpcap.so file in path :" + soPath);
						return;
					}
					// Files.copy(is, dest.toPath());//(Files class use need Java1.7+)
					FileUtils.copyFile(soSource, dest);// (Files class use need Java1.7+)
					logger.info("file does not exist in path : " + dest.toPath() + " ,copy a new one!");
				} catch (IOException e) {
					logger.error("Files deal exception", e);
				}
			} else {
				logger.info("file already exist in path : " + dest.toPath());
			}
			return;
		}

		// windows时处理
		Resource dllPath = null;
		if ("32".endsWith(architecture))
			dllPath = this.getFilePath("32/Jpcap.dll");
		else
			dllPath = this.getFilePath("64/Jpcap_x64.dll");

		// dllPath = dllPath.substring(1);



		// File dest = new File("Jpcap.dll");
		File dest = new File(sun_boot_library_path + File.separator + "Jpcap.dll");
		if (dest.exists()) {
			// dest.delete();//每次删除是需要重新复制32/64对应的dll，因为从名称无法判断32/64
			logger.info("file already exist in path : " + dest.toPath() + " !");
		} else {
			try {
				File source = dllPath.getFile();
				if (!source.exists()) {
					logger.error("can not find jpcap.dll file in path :" + dllPath);
					return;
				}
				FileUtils.copyFile(source, dest);// (Files class use need Java1.7+)
				logger.info("file does not exist in path : " + dest.toPath() + " ,copy a new one!");
			} catch (IOException e) {
				logger.error("Files deal exception", e);
			}
		}

	}

//	/**
//	 * TODO TODO 使用NativeLibrary不生效....
//	 */
//	@Deprecated
//	public void prepareDependantDll2() {
//		Properties props = System.getProperties();
//		String architecture = String.valueOf(props.get("sun.arch.data.model"));// sun.arch.data.model=32 或者64
//
//		String dllPath = null;
//
//		if ("32".endsWith(architecture))
//			dllPath = this.getFilePath("32/Jpcap.dll");
//		else
//			dllPath = this.getFilePath("64/Jpcap_x64.dll");
//
//		dllPath = dllPath.replace("%20", " ");
//		dllPath = dllPath.replace("%23", "#");
//		// dllPath = dllPath.substring(1);
//
//		logger.info(dllPath);
//		// dllPath=dllPath.substring(1,dllPath.length()).replace("/", "\\");
//		// File file = new File(dllPath + "/Jpcap.dll");
//		File source = new File(dllPath);
//		if (!source.exists())
//			logger.info(source.getAbsolutePath() + " do not exist!");
//
//		File dest = new File(System.getProperty("user.dir") + "\\tmp\\Jpcap.dll");
//		if (dest.exists())
//			dest.delete();// 每次删除是需要重新复制32/64对应的dll，因为从名称无法判断32/64
//
//		try {
//			new File(System.getProperty("user.dir") + "\\tmp").mkdir();
//			Files.copy(source.toPath(), dest.toPath());
//		} catch (IOException e) {
//			// TODO 自动生成的 catch 块
//			e.printStackTrace();
//		}
//
//		logger.info(dllPath);
//		logger.info(source.getAbsolutePath());
//		logger.info(dest.getAbsolutePath());
//
//		NativeLibrary.addSearchPath("jpcap", System.getProperty("user.dir") + "\\tmp");// jna.jar:com.sun.jna.NativeLibrary
//		// System.load(dest.getAbsolutePath());
//		System.loadLibrary("jpcap");
//		// System.out.println(props.get("java.library.path"));
//	}

	public Resource getFilePath(String fileName) {
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		String classPathName = "classpath*:*" + fileName;
		Resource[] resources;
		try {
			resources = resolver.getResources(classPathName);
			if (resources != null) {
				return resources[0];
			} else {
				return null;
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}
}
